﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.IO.Ports;
using System.Diagnostics;
using System.IO;

namespace Argos.Activities.MessageDelivery
{
	/// <summary>
	/// Sends SMS messages over a GSM/GPRS modem.
	/// </summary>
	internal class SMSDispatcher 
	{
		public SMSDispatcher(string simPIN, string portName)
			: this(simPIN, portName, 9600, Parity.None, 8, StopBits.One)
		{ }

		public SMSDispatcher(string simPIN, string portName, int baudRate)
			: this(simPIN, portName, baudRate, Parity.None, 8, StopBits.One)
		{ }

		/// <summary>
		/// Creates an instance of SMSDispatcher class.
		/// </summary>
		/// <param name="simPIN">PIN code for the SIM card which is inserted to the GSM modem.</param>
		/// <param name="portName">Name of the serial port which the GSM modem is connected to.</param>
		/// <param name="baudRate">Baud rate for the serial port connection.</param>
		/// <param name="parity">Parity for the serial port connection.</param>
		/// <param name="dataBits">Data bits for the serial port connection.</param>
		/// <param name="stopBits">Stop bits for the serial port connection.</param>
		public SMSDispatcher(string simPIN, string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits)
		{
			#region Check args
			if (simPIN == null)
			{
				throw new ArgumentNullException("simPIN");
			}
			if (portName == null)
			{
				throw new ArgumentNullException("portName");
			}
			if (baudRate < 1)
			{
				throw new ArgumentException("The parameter value must be greater than 0.", "baudRate");
			}
			#endregion

			if (!SerialPort.GetPortNames().Contains<string>(portName))
			{
				throw new ArgumentException(string.Format("Serial port named {0} does not exist.", portName), "portName");
			}

			this.simPIN = simPIN;
			this.portName = portName;
			this.baudRate = baudRate;
			this.paritySetting = parity;
			this.dataBits = dataBits;
			this.stopBitsSetting = stopBits;
		}

		private static object SyncRoot = new object();

		private string portName;
		private int baudRate;
		private Parity paritySetting;
		private int dataBits;
		private StopBits stopBitsSetting;
		private string simPIN;
		private bool _isDataReceived = false;

		private SerialPort GetPort()
		{
			SerialPort port = new SerialPort(portName, baudRate, paritySetting, dataBits, stopBitsSetting);
			port.NewLine = ((char)13).ToString();
			port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);
			return port;
		}

		/// <summary>
		/// An event handler for capturing request receive events from the serial port.
		/// </summary>
		private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
		{
			_isDataReceived = true;
		}

		/// <summary>
		/// Ensures that a pin code is entered to the SIM card, if one is required.
		/// </summary>
		private void EnsurePIN(SerialPort port)
		{
			string response = WriteAndGetResponse(@"AT+CPIN?", port);
			if (!response.Contains("READY"))
			{
				response = WriteAndGetResponse(string.Format(@"AT+CPIN={0}", simPIN), port);
			}
		}

		/// <summary>
		/// Writes a 'request' to the GSM modem, and receives a ¨'response' from the GMS modem.
		/// </summary>
		/// <param name="request">The request.</param>
		/// <returns>The response.</returns>
		private string WriteAndGetResponse(string request, SerialPort port)
		{
			_isDataReceived = false;
			port.WriteLine(request);
			Debug.WriteLine(string.Format("TO MODEM: {0}", request));
			int c = 0;
			// Wait until data is received or 3 seconds has passed.
			while (!_isDataReceived && c < 30)
			{
				c++;
				System.Threading.Thread.Sleep(10);
			}
			if (_isDataReceived)
			{
				string data = port.ReadExisting();
				Debug.WriteLine(string.Format("FROM MODEM: {0}", data));
				data.Replace(request + port.NewLine, "");
				return data;
			}
			else
			{
				Debug.WriteLine("MODEM DID NOT RESPOND WITHIN 3 SECONDS");
				return null;
			}
		}

		/// <summary>
		/// Returns GSM modem information.
		/// </summary>
		/// <returns></returns>
		public string GetModemInfo()
		{
			lock (SyncRoot)
			{
				SerialPort port = GetPort();
				port.Open();
				string mf = WriteAndGetResponse(@"AT+CGMI", port).Replace(Environment.NewLine + "OK", "").Replace(Environment.NewLine, "");
				string mn = WriteAndGetResponse(@"AT+CGMM", port).Replace(Environment.NewLine + "OK", "").Replace(Environment.NewLine, "");
				string sv = WriteAndGetResponse(@"AT+CGMR", port).Replace(Environment.NewLine + "OK", "").Replace(Environment.NewLine, "");
				port.Close();
				return string.Format("{0} {1} {2}", mf, mn, sv);
			}
		}

		/// <summary>
		/// Sends an SMS message over the GSM modem.
		/// </summary>
		/// <param name="recipientMSISDN">MSISDN of the recipient.</param>
		/// <param name="message">The message text.</param>
		public void SendSMS(string recipientMSISDN, string message)
		{
			lock (SyncRoot)
			{
				SerialPort port = GetPort();
				port.Open();
				string response;
				try
				{
					EnsurePIN(port);
					string request = @"AT+CMGF=1";
					response = WriteAndGetResponse(request, port);
					request = @"AT+CSCA?";
					response = WriteAndGetResponse(request, port);

					// It would be great if one could examine responses to these commands.
					// However, at least the HUAWEI E220 GRPS modem does not always respond with ok to these commands.
					// The same behavior occurs when typing the AT+ commands from HyperTerminal, so it's most
					// likely a modem issue.
					request = string.Format(@"AT+CMGS=""{0}""", recipientMSISDN);
					response = WriteAndGetResponse(request, port);
					response = WriteAndGetResponse(message.Left(160), port);
				}
				finally
				{
					try
					{
						response = WriteAndGetResponse(((char)26).ToString(), port);
					}
					finally
					{
						port.Close();
					}
				}
				

			}
		}
	}
}
